package sc.digital.human;

import com.google.gson.Gson;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * <a href="https://ai.baidu.com/ai-doc/UNIT/Lkipmh0tz">智能对话定制与服务平台 UNIT</a>
 */
public class Chat {
    private static final Logger logger = LoggerFactory.getLogger(Chat.class);
    private final OkHttpClient httpClient = new OkHttpClient.Builder().readTimeout(60, TimeUnit.SECONDS).build();
    private final Token token = new Token();
    private final Gson gson = new Gson();
    private final List<Message> messages = new ArrayList<>();
    private String sessionId = "";

    public Chat() {
    }

    /**
     * 文心一言
     *
     * @param content  对话内容
     * @param callback 回调
     */
    public void talk(String content, Callback callback) {
        this.token.refresh((credentials) -> {
            if (credentials == null) {
                callback.result(null);
            } else {
                Message message = new Message(Role.user, content);
                this.messages.add(message);
                this.httpClient.newCall(new Request.Builder()
                        .post(RequestBody.create(MediaType.parse("application/json")
                                , this.gson.toJson(new Messages(this.messages.toArray(new Message[0])))))
                        .url(String.format(Bot.toURL(Config.CHAT_BOT_NAME), credentials.access_token))
                        .build()).enqueue(new okhttp3.Callback() {
                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        if (logger.isInfoEnabled()) {
                            logger.info("文心一言：{}", response);
                        }
                        if (!response.isSuccessful() || response.body() == null) {
                            messages.remove(message);
                            callback.result(null);
                        } else {
                            String json = response.body().string();
                            if (logger.isInfoEnabled()) {
                                logger.info("文心一言：{}", json);
                            }
                            Result result = gson.fromJson(json, Result.class);
                            if (logger.isInfoEnabled()) {
                                logger.info("~~~~~~~~~~ 问：{}", content);
                                logger.info("~~~~~~~~~~ 答：{}", result.result);
                            }
                            callback.result(result.result);
                            if (result.result == null) {
                                messages.remove(message);
                            } else {
                                messages.add(new Message(Role.assistant, result.result));
                            }
                        }
                    }

                    @Override
                    public void onFailure(Call call, IOException e) {
                        if (logger.isErrorEnabled()) {
                            logger.error("文心一言：{}", e.getMessage(), e);
                        }
                        messages.remove(message);
                        callback.result(null);
                    }
                });

            }
        }, Config.CHAT_APP_KEY, Config.CHAT_SECRET_KEY);
    }

    /**
     * 智能对话
     *
     * @param token    Token
     * @param query    提问
     * @param callback 回调
     */
    public void talk(Token token, String query, Callback callback) {
        token.refresh((credentials) -> {
            if (credentials == null) {
                callback.result(null);
            } else {
                this.httpClient.newCall(new Request.Builder()
                        .post(RequestBody.create(MediaType.parse("application/json")
                                , gson.toJson(new ChatRequest(sessionId, query))))
                        .url(String.format(Ver.toURL(Config.CHAT_VERSION), credentials.access_token))
                        .build()).enqueue(new okhttp3.Callback() {
                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        if (logger.isInfoEnabled()) {
                            logger.info("智能对话：{}", response);
                        }
                        if (!response.isSuccessful() || response.body() == null) {
                            callback.result(null);
                        } else {
                            String json = response.body().string();
                            if (logger.isInfoEnabled()) {
                                logger.info("智能对话：{}", json);
                                logger.info("~~~~~~~~~~ {}", query);
                                logger.info("~~~~~~~~~~ {}", sessionId);
                            }
                            ChatResponse result = gson.fromJson(json, ChatResponse.class);
                            if (!result.isSuccess()) {
                                callback.result(result.error_msg);
                            } else {
                                sessionId = result.result.session_id;
                                for (ChatResponse.Response res : (result.result.responses == null
                                        ? result.result.response_list : result.result.responses)) {
                                    if (res.isSuccess()) {
                                        if (logger.isInfoEnabled()) {
                                            logger.info("~~~~~~~~~~ 技能：{}", res.origin);
                                        }
                                        for (ChatResponse.Action act : (res.actions == null ? res.action_list : res.actions)) {
                                            if (logger.isInfoEnabled()) {
                                                logger.info("动作：{}\t置信度：{}\t话术：{}"
                                                        , ActionType.asString(act.type), act.confidence, act.say);
                                                if (act.refine_detail != null) {
                                                    logger.info("澄清与引导：{}", act.refine_detail);
                                                }
                                                if (act.options != null) {
                                                    logger.info("选项列表：{}", act.options);
                                                }
                                                if (act.custom_reply != null) {
                                                    logger.info("自定义应答：{}", act.custom_reply);
                                                }
                                            }
                                        }
                                    }
                                }
                                callback.result(result.result.responses == null
                                        ? (result.result.response_list[0].isSuccess()
                                        ? result.result.response_list[0].action_list[0].say : null)
                                        : (result.result.responses[0].isSuccess()
                                        ? result.result.responses[0].actions[0].say : null));
                            }
                            if (logger.isInfoEnabled()) {
                                logger.info("~~~~~~~~~~ {}", sessionId);
                            }
                        }
                    }

                    @Override
                    public void onFailure(Call call, IOException e) {
                        if (logger.isErrorEnabled()) {
                            logger.error("智能对话：{}", e.getMessage(), e);
                        }
                        callback.result(null);
                    }
                });
            }
        });
    }

    interface Callback {
        /**
         * 获取智能对话结果
         *
         * @param result 智能对话结果
         */
        void result(String result);
    }

    static class ChatRequest {
        public String version;
        public String service_id;
        public String[] skill_ids;
        public String log_id;
        public String session_id;
        public Request request;

        public ChatRequest() {
            this.version = Config.CHAT_VERSION;
            this.service_id = Config.SERVICE_ID;
            this.skill_ids = Config.SKILL_ID;
            this.log_id = UUID.randomUUID().toString();
        }

        public ChatRequest(String session_id, String query) {
            this();
            this.session_id = session_id;
            this.request = new Request(query);
        }

        static class Request {
            public String user_id;//2.0
            public String terminal_id;//3.0
            public String query;//本轮请求query（用户说的话）
            public QueryInfo query_info;

            public Request() {
                this.user_id = "UNIT_DEV_" + Config.APP_ID;
                this.terminal_id = user_id;
                this.query_info = new QueryInfo();
            }

            public Request(String query) {
                this();
                this.query = query;
            }
        }

        static class QueryInfo {
            public String type;
            public String source;

            public QueryInfo() {
                this.type = "TEXT";
                this.source = "ASR";
            }
        }
    }

    static class ChatResponse {
        public int error_code;
        public String error_msg;
        public Result result;

        public boolean isSuccess() {
            return 0 == error_code
                    && result != null
                    && ((result.response_list != null && result.response_list.length > 0)
                    || (result.responses != null && result.responses.length > 0));
        }

        static class Result {
            public String session_id;
            public Response[] response_list;//2.0
            public Response[] responses;//3.0
        }

        static class Response {
            public int status;
            public String origin;//技能或机器人
            public Action[] action_list;//2.0
            public Action[] actions;//3.0

            public boolean isSuccess() {
                return 0 == status
                        && ((action_list != null && action_list.length > 0)
                        || (actions != null && actions.length > 0));
            }
        }

        static class Action {
            public String say;//应答话术
            public String type;//动作类型
            public float confidence;//动作置信度
            public Object refine_detail;//澄清与引导(type=clarify/guide/faqguide)时有效，表达澄清或引导的详细信息。
            public Object options;//选项列表
            public Object custom_reply;//自定义应答
        }
    }

    enum ActionType {
        clarify("澄清"),
        satisfy("满足"),
        guide("引导到对话意图"),
        faqguide("引导到问答意图"),
        understood("理解达成"),
        failure("理解失败"),
        chat("聊天话术"),
        event("触发事件");
        public final String name;

        ActionType(String name) {
            this.name = name;
        }

        public static String asString(String type) {
            for (ActionType actionType : values()) {
                if (actionType.name().equalsIgnoreCase(type)) {
                    return String.format("%s(%s)", type, actionType.name);
                }
            }
            return type;
        }
    }

    static class Messages {
        public Message[] messages;

        public Messages(Message[] messages) {
            this.messages = messages;
        }
    }

    static class Message {
        public Role role;
        public String content;

        public Message(Role role, String content) {
            this.role = role;
            this.content = content;
        }
    }

    static class Result {
        public String result;
    }

    enum Role {
        user, assistant
    }

    enum Model {
        UNIT("智能对话"), ERNIE("文心一言");
        public final String name;

        Model(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    enum Ver {
        V2("2.0", "https://aip.baidubce.com/rpc/2.0/unit/service/chat?access_token=%s"),
        V3("3.0", "https://aip.baidubce.com/rpc/2.0/unit/service/v3/chat?access_token=%s");
        final String name;
        final String url;

        Ver(String name, String url) {
            this.name = name;
            this.url = url;
        }

        static Ver toVer(String name) {
            for (Ver ver : Ver.values()) {
                if (ver.name.equalsIgnoreCase(name)) {
                    return ver;
                }
            }
            return V2;
        }

        static String toURL(String name) {
            return toVer(name).url;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    enum Bot {
        ERNIE_8K("ERNIE-3.5-8K"
                , "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token=%s"),
        ERNIE_4_8K("ERNIE-4-8K"
                , "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro?access_token=%s"),
        ERNIE_Speed_8K("ERNIE-Speed-8K"
                , "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie_speed?access_token=%s"),
        ERNIE_Lite_8K("ERNIE-Lite-8K"
                , "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant?access_token=%s");

        final String name;
        final String url;

        Bot(String name, String url) {
            this.name = name;
            this.url = url;
        }

        static Bot toBot(String name) {
            for (Bot bot : Bot.values()) {
                if (bot.name.equalsIgnoreCase(name)) {
                    return bot;
                }
            }
            return ERNIE_Speed_8K;
        }

        static String toURL(String name) {
            return toBot(name).url;
        }

        @Override
        public String toString() {
            return name;
        }
    }

}
